package com.leonardo.beanme.metadatahandlers;

import java.io.IOException;
import java.util.Hashtable;
import java.util.Vector;

import MAR.Core.Method;
import MAR.Core.Reflector;

import com.leonardo.beanme.exceptions.NoSuchBeanException;
import com.leonardo.beanme.exceptions.TypeInstantiationException;
import com.leonardo.beanme.exceptions.XmlReadingException;
import com.leonardo.beanme.fields.TypeToField;
import com.leonardo.beanme.metadatahandlers.tags.FieldTagHandler;
import com.leonardo.beanme.metadatahandlers.tags.FieldTypeTagHandler;
import com.leonardo.beanme.metadatahandlers.tags.GetterTagHandler;
import com.leonardo.beanme.metadatahandlers.tags.LabelTagHandler;
import com.leonardo.beanme.metadatahandlers.tags.SetterTagHandler;

public class MetadataRepository {

	private Hashtable customConfigs = new Hashtable();
	private static MetadataRepository instance;
	
	public static MetadataRepository getInstance() {
		if(instance == null)
			instance = new MetadataRepository();
		return instance;
	}
	
	private MetadataRepository() {
		configure();
	}
	
	private void configure() {
		try {
			customConfigs = BeanMetadataReader.getBeanMetadataReader().getMetadataContainers();
		} catch(XmlReadingException ex) {
			throw ex;
		} catch (IOException ex) {
			throw new XmlReadingException("Error opening xml");
		} catch(ClassNotFoundException ex) {
			throw new XmlReadingException("Invalid type-field");
		} catch(Exception ex) {
			ex.printStackTrace();
			System.out.println("No configuration");
		}
	}
	
	public FormMetadataContainer getConfiguration(Class clazz, String beanId) 
					throws TypeInstantiationException, ClassNotFoundException {
		Object o;
		try {
			o = clazz.newInstance();
		} catch (IllegalAccessException ex) {
			throw new TypeInstantiationException(clazz);
		} catch(InstantiationException ex) {
			throw new TypeInstantiationException(clazz);
		}
		Reflector reflector = (Reflector) o;
		
		FormMetadataContainer conf = null;
		if(beanId != null) {
			if(!customConfigs.containsKey(beanId))
				throw new NoSuchBeanException(beanId);
			conf = (FormMetadataContainer) customConfigs.get(beanId);
		}
		return completeDefaultConfiguration(conf, reflector);
	}
	
	private FormMetadataContainer completeDefaultConfiguration(FormMetadataContainer conf, Reflector reflector) 
				throws TypeInstantiationException, ClassNotFoundException {
		if(conf == null)
			conf = new FormMetadataContainer(reflector.getClass().getName(), FormMetadataContainer.ADDITIONAL);
		
		Vector methods = reflector.getMethods();
		for(int i = 0; i < methods.size(); i++) {
			Method method = (Method) methods.elementAt(i);
			if(acceptableSetMethod(method)) {
				String fieldName = method.getName().substring(3,4).toLowerCase();
				if(method.getName().length() > 4)
					fieldName += method.getName().substring(4); 
				
				FieldMetadataContainer field = conf.getFieldOrNew(fieldName);
				if(field == null)
					continue;
				
				if(!field.constains(FieldTagHandler.class))	addName(field, fieldName);
				if(!field.constains(LabelTagHandler.class)) addLabel(field, fieldName);
				if(!field.constains(FieldTypeTagHandler.class)) addType(field, method);
				if(!field.constains(GetterTagHandler.class)) addGetter(field, fieldName);
				if(!field.constains(SetterTagHandler.class)) addSetter(field, fieldName);
				
			}
		}
		return conf;
	}
	
	private void addName(FieldMetadataContainer field, String name) {
		FieldTagHandler fieldTag = new FieldTagHandler();
		fieldTag.setName(name);
		field.addHandler(fieldTag);
	}
	
	private void addLabel(FieldMetadataContainer field, String name) {
		LabelTagHandler labelTag = new LabelTagHandler();
		labelTag.setInnerText(name);
		field.addHandler(labelTag);
	}
	
	private void addType(FieldMetadataContainer field, Method setter) {
		Class fieldType = setter.getParameterTypes()[0];
		Class inputFieldType = TypeToField.getField(fieldType);
		FieldTypeTagHandler fieldTypeTag = new FieldTypeTagHandler();
		fieldTypeTag.setInnerText(inputFieldType.getName());
		field.addHandler(fieldTypeTag);
	}
	
	private void addGetter(FieldMetadataContainer field, String fieldName) {
		GetterTagHandler getterTag = new GetterTagHandler();
		getterTag.setInnerText("get" + Character.toUpperCase(fieldName.charAt(0)) + fieldName.substring(1));
		field.addHandler(getterTag);
	}
	
	private void addSetter(FieldMetadataContainer field, String fieldName) {
		SetterTagHandler setterTag = new SetterTagHandler();
		setterTag.setInnerText("set" + Character.toUpperCase(fieldName.charAt(0)) + fieldName.substring(1));
		field.addHandler(setterTag);
	}
	
	private boolean acceptableSetMethod(Method method) {
		if(method == null || !method.getName().startsWith("set") 
		|| !TypeToField.acceptType(method.getParameterTypes()[0]))
			return false;
		return true;
		//Checks if setter is accessible
//		try {
//			Class type = method.getParameterTypes()[0];
//			Reflector refl = (Reflector) method.getDeclaringClass().newInstance();
//			method.invoke(refl, new Object[] {null});
//			return true;
//		} catch(Exception ex) {
//			ex.printStackTrace();
//			return false;
//		}
	}
		
}